Handwriting Classification(MNIST)

위스콘신-매디슨 대학교의 딥러닝 과목 강의 슬라이드
https://sebastianraschka.com/pdf/lecture-notes/stat479ss19/L08_logistic_slides.pdf
https://sebastianraschka.com/pdf/lecture-notes/stat479ss19/L09_mlp_slides.pdf
MNIST(Mixed National Institute of Standards and Technology) DATA SET
MNIST는 다층 신경망으로 분류하기 위해 잘 쓰이는 손글씨 데이터셋이다.
MNIST는 미국 NIST(National Institute of Standars and Technology)에서 만든 두개의 데이터셋으로 구성되어 있다.

훈련데이터셋은 다른 250명의 사람이 쓴 손글씨 숫자이다.
50%는 고등학교 학생이고 50%는 인구 조사국 직원이다.
테스트 데이터셋 또한 같은 비율로 구성되어 있다.

이미지는 바이트 포맷으로 저장되어 있어 넘파이 배열로 읽을 수 있다.
http://yann.lecun.com/exdb/mnist/
$gzip *ubyte.gz -d
import os
import struct
import numpy as np
def load_mnist(path, kind='train'):
labels_path=os.path.join(path, '%s-labels-idx1-ubyte' %kind)
images_path=os.path.join(path, '%s-images-idx3-ubyte' %kind)
with open(labels_path, 'rb') as lbpath:
magic, n=struct.unpack('>II', lbpath.read(8))
labels=np.fromfile(lbpath, dtype=np.uint8)
with open(images_path, 'rb') as imgpath:
magic, num, rows, cols=struct.unpack(">IIII", imgpath.read(16))
images=np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784)
images=((images/255.)-.5)*2
return images, labels

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000801(2049) magic number (MSB first) 
0004     32 bit integer  60000            number of items 
0008     unsigned byte   ??               label 
0009     unsigned byte   ??               label 
........ 
xxxx     unsigned byte   ??               label

The labels values are 0 to 9.

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000803(2051) magic number 
0004     32 bit integer  60000            number of images 
0008     32 bit integer  28               number of rows 
0012     32 bit integer  28               number of columns 
0016     unsigned byte   ??               pixel 
0017     unsigned byte   ??               pixel 
........ 
xxxx     unsigned byte   ??               pixel

Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

이진 코드파일은 위와 같은 구조로 형성되어 있다.
라벨(인덱스) 파일에는 처음 8바이트, 이미지 파일에는 처음 16바이트에 파일에 대한 정보가 들어 있다.
magic number에 파일 프로토콜이 저장, n(num)에 아이템(이미지) 개수 저장, rows에 행 개수, columns에 열 개수가 저장된다.

이후 데이터는 np.fromfile 메서드를 사용하여 이어지는 바이트를 넘파일 배열 형태로 읽는다.
imges에서 (len(labels), 784) 에서 784=28X28로 데이터 셋의 28X28 픽셀의 개수를 의미한다.
fmt 매개변수
>    : 빅 엔디언(big-endian)을 나타낸다. 바이트가 저장된 순서를 정의한다.
I     : 부호가 없는 정수를 의미한다.

ex)
>II        // 빅 엔디언 부호가 없는 정수X2 (8byte)
>IIII      // 빅 엔디언 부호가 없는 정수X4 (16byte)
imges=((images/255.)-.5)*2
위의 코드는 MNIST 픽셀 값을 -1에서 1사이로 정규화 한다.(원래는 0-255, 2^8=byte)
그레디언트 기반의 최적화가 이와 같은 조건에서 훨씬 안정적이다.(여기서는 픽셀단위로 스케일을 조정)
배치 정규화
입력 데이터의 스케일 조정을 통해 그레이디언트 기반 최적화의 수렴을 향상시키기 위해 널리 사용하는 기법이다.
X_train, y_train=load_mnist('', kind='train')
print(': %d, : %d' %(X_train.shape[0], X_train.shape[1]))
X_test, y_test=load_mnist('', kind='t10k')
print(': %d, : %d' %(X_test.shape[0], X_test.shape[1]))

행: 60000, 열: 784

행: 10000, 열: 784

이미지 출력
import matplotlib.pyplot as plt
fig, ax=plt.subplots(nrows=2, ncols=5, sharex=True, sharey=True)
ax=ax.flatten()
for i in range(10):
img=X_train[y_train==i][0].reshape(28, 28)
ax[i].imshow(img, cmap='Greys')
ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.show()
숫자 7 이미지 출력
fig, ax=plt.subplots(nrows=5, ncols=5, sharex=True, sharey=True)
ax=ax.flatten()
for i in range(25):
img=X_train[y_train==7][i].reshape(28, 28)
ax[i].imshow(img, cmap='Greys')
ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.show()
넘파이 배열(다차원 배열) 디스크에 저장(savez)
pickle은 모듈을 직렬적으로 저장하는 도구이다. 하지만 savez 함수는 넘파이 배열을 저장하는데 최적화 되어 있다.
savez 함수는 데이터를 압축하여 .npz 포맷 파일을 담고 있는 .npz 파일을 만든다.
https://numpy.org/doc/stable/reference/generated/numpy.savez.html

savez_compressed는 savez와 사용법은 같지만 파일 크기를 더욱 작게 압축할 수 있다.
넘파일 배열 저장 및 압축
import numpy as np
np.savez_compressed('mnist_scaled.npz', X_train=X_train, y_train=y_train, X_test=X_test, y_test=y_test)
넘파일 배일 불러오기
mnist=np.load('mnist_scaled.npz')
mnist.files

['X_train', 'y_train', 'X_test', 'y_test']

files 속성에 입력 배열이 저장되어 있다.
X_train, y_train, X_test, y_test=[mnist[f] for f in mnist.files]
from scikit-learn get MNIST
scikit-learn의 fetch_openml 함수를 사용하면 MNIST 데이터셋을 로드할 수 있다.
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
X, y=fetch_openml('mnist_784', version=1, return_X_y=True)
y=y.astype(int)
X=((X/255.)-.0)*2
X_train, X_test, y_train, y_test=train_test_split(X, y, test_size=10000, random_state=123, stratify=y)